package liuch.NioSocket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class NioSocketServer {

    private Selector selector;

    private final static int PORT = 9900;

    private HashSet<String> online = new HashSet<>();

    private Charset charset = Charset.forName("UTF-8");

    private static String USER_EXIST = "system message: user exist, please change a name";

    private static String USER_CONTENT_SPLIT = "#@#";

    private void init() throws IOException {
        // 打开一个选择器
        this.selector = Selector.open();

        // 开启通道，并指定通道，并将选择器绑定到通道上
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        InetSocketAddress address = new InetSocketAddress(PORT);
        serverSocket.bind(address);
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("server is linstening...");

        // 等待客户端的连接
        while (true) {
            int nums = this.selector.select();
            if (nums <= 0) {
                continue;
            }

            // 存在连接
            Set<SelectionKey> selectionKeySet = this.selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeySet.iterator();
            while (iterator.hasNext()) {
                // 得到当前的选择器
                SelectionKey selectionKey = iterator.next();
                iterator.remove();

                // 处理当前的选择器
                dealWithSelectionKey(serverSocketChannel, selectionKey);
            }
        }
    }

    /**
     * 通道处理当前选择器
     *
     * @param serverSocketChannel
     * @param selectionKey
     */
    private void dealWithSelectionKey(ServerSocketChannel serverSocketChannel, SelectionKey selectionKey) throws IOException {
        if (selectionKey.isAcceptable()) {
            //接收客户端
            SocketChannel socketChannel = serverSocketChannel.accept();
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //注册选择器，并设置为读取模式，收到一个连接请求，然后起一个SocketChannel，并注册到selector上，之后这个连接的数据，就由这个socketchannel处理。
            socketChannel.register(selector, SelectionKey.OP_READ);
            //将此对应的channel设置为准备接收其他客户端的请求
            selectionKey.interestOps(SelectionKey.OP_ACCEPT);

            System.out.println("Server is listening from client :" + socketChannel.socket().getRemoteSocketAddress());

            socketChannel.write(charset.encode("Please input your name:"));
        }
        // 处理来自客户端的数据读取请求
        else if (selectionKey.isReadable()) {
            //得到该key对应的channel，其中有数据需要读取
            SocketChannel sc = (SocketChannel) selectionKey.channel();
            StringBuffer content = new StringBuffer();
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            try {
                //得到客户端传过来的消息
                while (sc.read(buffer) > 0) {
                    buffer.flip();
                    content.append(charset.decode(buffer));
                }

                //将此对应的channel设置为准备下一次接受数据
                selectionKey.interestOps(SelectionKey.OP_READ);
            } catch (Exception e) {
                e.printStackTrace();
                selectionKey.cancel();
                sc.close();
            }
            //如果内容不为空
            if (content.length() > 0) {

                //拆分规则
                String[] msgArr = content.toString().split(USER_CONTENT_SPLIT);

                //注册名字
                if (msgArr.length == 1) {

                    //用户已经存在，则直接返回
                    if (online.contains(msgArr[0])) {
                        sc.write(charset.encode(USER_EXIST));
                    } else {
                        String name = msgArr[0];
                        online.add(name);

                        int onlineNum = this.onlineTotal();
                        String msg = "welcome " + name + " to chat room,current online people num is:" + onlineNum;

                        //通知所有的人
                        broadCast(selector, null, msg);
                    }
                }
                //聊天内容
                else if (msgArr.length > 1) {
                    String name = msgArr[0];
                    String message = content.substring(name.length() + USER_CONTENT_SPLIT.length());
                    message = name + " say " + message;
                    if (online.contains(name)) {

                        //不回发给发送此内容的客户端
                        broadCast(selector, sc, message);
                    }
                }
            }
        }
    }


    /**
     * 通知所有人
     *
     * @param selector 选择器
     * @param except   不通知的客户端
     * @param msg      消息
     * @throws IOException
     * @author 1
     */
    private void broadCast(Selector selector, SocketChannel except, String msg) throws IOException {

        for (SelectionKey key : selector.keys()) {
            Channel channel = key.channel();
            if (channel instanceof SocketChannel && channel != except) {
                SocketChannel socketChannel = (SocketChannel) channel;
                socketChannel.write(charset.encode(msg));
            }
        }
    }

    /**
     * 得到在线总人数
     *
     * @return
     * @author 1
     */
    private int onlineTotal() {

        int num = 0;
        for (SelectionKey key : this.selector.keys()) {
            Channel targetchannel = key.channel();
            if (targetchannel instanceof SocketChannel) {
                num++;
            }
        }
        return num;
    }

    public static void main(String[] args) throws IOException {
        new NioSocketServer().init();
    }
}
